package models;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import models.exceptions.EvaluatorException;

public class Evaluator
{

    protected Map<String, List<Card>> hands;
    protected Map<String, Evaluation> evaluations;
    protected String                  winner;

    public Evaluator()
    {
        this.hands = new HashMap<String, List<Card>>();
        this.evaluations = new HashMap<String, Evaluation>();
    }

    public void addHand(String uuid, List<Card> hand) throws EvaluatorException
    {
        if (hand.size() != 5)
        {
            throw new EvaluatorException(
                    "Evaluator only works with hands size == 5.");
        }
        if (this.hands.containsKey(uuid))
        {
            throw new EvaluatorException("This key (" + uuid
                    + ") has been already added.");
        }
        this.hands.put(uuid, hand);
    }

    public Winner getWinner() throws EvaluatorException
    {
        if (this.hands.size() < 1)
        {
            throw new EvaluatorException("Not enough hands available.");
        }
        String currentWinner = "";
        Evaluation currentEval;
        Evaluation higherEval;
        boolean draw = false;

        List<Card> currentHand;
        String currentUuid;
        Map.Entry<String, List<Card>> pair;
        Iterator<Map.Entry<String, List<Card>>> it = this.hands.entrySet()
                .iterator();
        pair = it.next();
        currentUuid = pair.getKey();
        currentHand = pair.getValue();
        higherEval = this.evaluate(currentHand);
        this.evaluations.put(currentUuid, higherEval);
        currentWinner = currentUuid;
        while (it.hasNext())
        {
            pair = it.next();
            currentUuid = pair.getKey();
            currentHand = pair.getValue();
            currentEval = this.evaluate(currentHand);
            this.evaluations.put(currentUuid, currentEval);
            if (higherEval.compareTo(currentEval) < 0)
            {
                higherEval = currentEval;
                currentWinner = currentUuid;
                draw = false;
            }
            else if (higherEval.compareTo(currentEval) == 0)
            {
                draw = true;
            }
        }
        return draw ? null : new Winner(currentWinner, higherEval);
    }

    public Map<String, Evaluation> getEvaluations()
    {
        return this.evaluations;
    }

    public Evaluation evaluate(List<Card> hand) throws EvaluatorException
    {
        if (hand.size() != 5)
        {
            throw new EvaluatorException(
                    "Evaluator only accepts hands with size == 5.");
        }
        Card c0 = hand.get(0);
        Card c1 = hand.get(1);
        Card c2 = hand.get(2);
        Card c3 = hand.get(3);
        Card c4 = hand.get(4);

        int c0r = c0.rank.toInteger();
        int c1r = c1.rank.toInteger();
        int c2r = c2.rank.toInteger();
        int c3r = c3.rank.toInteger();
        int c4r = c4.rank.toInteger();

        int c0s = c0.suit.toInteger();
        int c1s = c1.suit.toInteger();
        int c2s = c2.suit.toInteger();
        int c3s = c3.suit.toInteger();
        int c4s = c4.suit.toInteger();

        // Straight flush.
        if ((c0s == c1s) && (c1s == c2s) && (c2s == c3s) && (c3s == c4s))
        {
            if (((c0r + 4) == c1r) && ((c1r + 4) == c2r) && ((c2r + 4) == c3r)
                    && ((c3r + 4) == c4r))
            {
                return new Evaluation(10000 + c4r);
            }
        }

        // Four of kind.
        if ((c0r == c1r) && (c1r == c2r) && (c2r == c3r))
        {
            return new Evaluation(9000 + c3r);
        }
        else if ((c1r == c2r) && (c2r == c3r) && (c3r == c4r))
        {
            return new Evaluation(9000 + c4r);
        }

        // Full house.
        if ((c0r == c1r) && (c1r == c2r) && (c3r == c4r))
        {
            return new Evaluation(8000 + c2.id);
        }
        else if ((c0r == c1r) && (c2r == c3r) && (c3r == c4r))
        {
            return new Evaluation(8000 + c4.id);
        }

        // Flush.
        if ((c0s == c1s) && (c1s == c2s) && (c2s == c3s) && (c3s == c4s))
        {
            Evaluation e = new Evaluation(7000 + c4r);
            e.addExtra(c3r);
            e.addExtra(c2r);
            e.addExtra(c1r);
            e.addExtra(c0r);
            return e;
        }

        // Straight.
        if (((c0r + 4) == c1r) && ((c1r + 4) == c2r) && ((c2r + 4) == c3r)
                && ((c3r + 4) == c4r))
        {
            return new Evaluation(6000 + c4r);
        }

        // Three of kind.
        if ((c0r == c1r) && (c1r == c2r))
        {
            return new Evaluation(5000 + c2r);
        }
        else if ((c1r == c2r) && (c2r == c3r))
        {
            return new Evaluation(5000 + c3r);
        }
        else if ((c2r == c3r) && (c3r == c4r))
        {
            return new Evaluation(5000 + c4r);
        }

        // Two pair.
        if (c0r == c1r)
        {
            if (c2r == c3r)
            {
                Evaluation e = new Evaluation(4000 + c2r);
                e.addExtra(c0r);
                e.addExtra(c1r);
                e.addExtra(c4r);
                return e;
            }
            else if (c3r == c4r)
            {
                Evaluation e = new Evaluation(4000 + c4r);
                e.addExtra(c0r);
                e.addExtra(c1r);
                e.addExtra(c2r);
                return e;
            }
        }
        else if (c1r == c2r)
        {
            if (c3r == c4r)
            {
                Evaluation e = new Evaluation(4000 + c4r);
                e.addExtra(c1r);
                e.addExtra(c2r);
                e.addExtra(c0r);
                return e;
            }
        }

        // One pair.
        if ((c0r == c1r) || (c1r == c2r) || (c2r == c3r) || (c3r == c4r))
        {
            Evaluation e;
            if (c0r == c1r)
            {
                e = new Evaluation(1000 + c0r);
                e.addExtra(c4r);
                e.addExtra(c3r);
                e.addExtra(c2r);
            }
            else if (c1r == c2r)
            {
                e = new Evaluation(1000 + c1r);
                e.addExtra(c4r);
                e.addExtra(c3r);
                e.addExtra(c0r);
            }
            else if (c2r == c3r)
            {
                e = new Evaluation(1000 + c2r);
                e.addExtra(c4r);
                e.addExtra(c1r);
                e.addExtra(c0r);
            }
            else
            {
                e = new Evaluation(1000 + c3r);
                e.addExtra(c2r);
                e.addExtra(c1r);
                e.addExtra(c0r);
            }
            return e;
        }

        // High card.
        Evaluation e = new Evaluation(c4r);
        e.addExtra(c3r);
        e.addExtra(c2r);
        e.addExtra(c1r);
        e.addExtra(c0r);
        return e;
    }

    public static class Evaluation implements Comparable<Evaluation>
    {
        public int           main;
        public List<Integer> extra;

        public Evaluation(int main)
        {
            this.main = main;
            this.extra = new ArrayList<Integer>();
        }

        public void addExtra(int extra)
        {
            this.extra.add(extra);
        }

        @Override
        public int compareTo(Evaluation eval)
        {
            if (this.main < eval.main)
            {
                return -1;
            }
            else if (this.main > eval.main)
            {
                return 1;
            }
            else
            {
                for (int i = 0; i < this.extra.size(); ++i)
                {
                    if (this.extra.get(i) < eval.extra.get(i))
                    {
                        return -1;
                    }
                    else if (this.extra.get(i) > eval.extra.get(i))
                    {
                        return 1;
                    }
                }
            }
            return 0;
        }

        @Override
        public String toString()
        {
            if (this.main >= 10000)
            {
                return "Straight Flush";
            }
            else if (this.main >= 9000)
            {
                return "Four of Kind";
            }
            else if (this.main >= 8000)
            {
                return "Full House";
            }
            else if (this.main >= 7000)
            {
                return "Flush";
            }
            else if (this.main >= 6000)
            {
                return "Straight";
            }
            else if (this.main >= 5000)
            {
                return "Three of Kind";
            }
            else if (this.main >= 4000)
            {
                return "Two Pair";
            }
            else if (this.main >= 1000)
            {
                return "One Pair";
            }
            else
            {
                return "High Card";
            }
        }
    }

    public static class Winner
    {
        public String     uuid;
        public Evaluation eval;

        public Winner(String uuid, Evaluation eval)
        {
            this.uuid = uuid;
            this.eval = eval;
        }
    }

}
